<!DOCTYPE html>
<html>
<head>
<title>RTCRtpSender.setParameters</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
</head>
<body>
<script>
promise_test(async t => {
  let pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  let pc2 = new RTCPeerConnection();

  t.add_cleanup(() => pc2.close());

  let stream = await navigator.mediaDevices.getUserMedia({ video: true });
  let videoSender = pc1.addTrack(stream.getVideoTracks()[0], stream);

  await exchangeOfferAnswer(pc1, pc2);
  let videoParameters = videoSender.getParameters();
  videoParameters.encodings[0].active = false;
  videoParameters.encodings[0].priority = 'high';
  videoParameters.encodings[0].maxBitrate = 1337000;
  await videoSender.setParameters(videoParameters);

  videoParameters = videoSender.getParameters();
  assert_equals(videoParameters.encodings[0].active, false);
  assert_equals(videoParameters.encodings[0].priority, 'high');
  assert_equals(videoParameters.encodings[0].maxBitrate, 1337000);
}, 'setParameters() changes getParameters() returned values');

promise_test(async t => {
  let pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  let pc2 = new RTCPeerConnection();

  t.add_cleanup(() => pc2.close());

  let stream = await navigator.mediaDevices.getUserMedia({ video: true });
  let videoSender = pc1.addTrack(stream.getVideoTracks()[0], stream);

  await exchangeOfferAnswer(pc1, pc2);
  let videoParameters = videoSender.getParameters();
  videoParameters.encodings[0].maxBitrate = 0xFFFFFFFF;
  await videoSender.setParameters(videoParameters);

  videoParameters = videoSender.getParameters();
  assert_equals(videoParameters.encodings[0].maxBitrate, 0xFFFFFFFF);
}, 'setParameters() with large maxBitrate changes getParameters() returned values');

promise_test(async (t) => {
  let pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  let pc2 = new RTCPeerConnection();

  t.add_cleanup(() => pc2.close());

  let stream = await navigator.mediaDevices.getUserMedia({ video: true }, 1);
  let videoSender = pc1.addTrack(stream.getVideoTracks()[0], stream);
  await exchangeOfferAnswer(pc1, pc2);

  let videoParameters = videoSender.getParameters();
  videoParameters.encodings[0].active = false;
  videoParameters.encodings[0].priority = 'high';
  videoParameters.encodings[0].maxBitrate = 1337000;
  await videoSender.setParameters(videoParameters);

  videoParameters.encodings[0].active = true;
  videoParameters.encodings[0].priority = 'low';
  videoParameters.encodings[0].maxBitrate = 1337001;
  return promise_rejects_dom(
    t,
    "InvalidStateError",
    videoSender.setParameters(videoParameters));
}, 'setParameters() with multiple calls after single getParameters()');

promise_test(async (t) => {
  let pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  let pc2 = new RTCPeerConnection();

  t.add_cleanup(() => pc2.close());

  let stream = await navigator.mediaDevices.getUserMedia({ video: true }, 1);
  let videoSender = pc1.addTrack(stream.getVideoTracks()[0], stream);
  await exchangeOfferAnswer(pc1, pc2);

  return promise_rejects_dom(
    t,
    "InvalidStateError",
    videoSender.setParameters({
      transactionId: "",
      codecs: [],
      encodings: [],
      rtcp: {},
      headerExtensions: []
    }));
}, 'setParameters() fails without getParameters()');

/**
  * Tests for read only parameters check.
  * Those are mostly similar, so an array of object is used to generate them.
  * They have the following properties:
  * - name: name of the test
  * - transform: function transforming the parameters to trigger an error
  * - check: function checking if the transform can be applied
  */
var readOnlyParameters = [
  {
    name: "transactionId modification",
    transform: (p) => { p.transactionId = "foo"; },
  },
  {
    name: "codecs remove one",
    transform: (p) => { p.codecs.pop(); },
  },
  {
    name: "codecs add one",
    transform: (p) => {
      p.codecs.push({
        payloadType: 26,
        mimeType: "video/dummy",
        clockRate: 24000,
        channels: 0,
        sdpFmtpLine: "sdpFmtpLine",
      });
    },
  },
  {
    name: "codecs reorder",
    transform: (p) => { p.codecs.concat(p.codecs.splice(0, 1)); },
  },
  {
    name: "codecs.payloadtype modification",
    transform: (p) => { p.codecs[0].payloadType = 1337; },
  },
  {
    name: "codecs.mimeType modification",
    transform: (p) => { p.codecs[0].mimeType = "mime"; },
  },
  {
    name: "codecs.clockRate modification",
    transform: (p) => { p.codecs[0].clockRate = 42; },
  },
  {
    name: "codecs.channels modification",
    transform: (p) => { p.codecs[0].channels = 16; },
  },
  {
    name: "codecs.channels removed",
    transform: (p) => { delete p.codecs[0].channels; },
    check: (p) => { p.codecs[0].channels !== undefined },
  },
  {
    name: "codecs.sdpFmtpLine modification",
    transform: (p) => { p.codecs[0].sdpFmtpLine = "sdpFmtpLine"; },
  },
  {
    name: "codecs.sdpFmtpLine removed",
    transform: (p) => { delete p.codecs[0].sdpFmtpLine; },
    check: (p) => { return p.codecs[0].sdpFmtpLine !== undefined; },
  },
  {
    name: "encodings remove one",
    transform: (p) => { p.encodings.pop(); },
  },
  {
    name: "encodings add one",
    transform: (p) => { p.encodings.push({}); },
  },
  {
    name: "encodings.rid modification",
    transform: (p) => { p.encodings[0].rid = "rid"; },
  },
  {
    name: "headerExtensions remove one",
    transform: (p) => { p.headerExtensions.pop(); },
    check: (p) => { return p.headerExtensions.length != 0; },
  },
  {
    name: "headerExtensions add one",
    transform: (p) => {
      p.headerExtensions.push({
        uri: "uri",
        id: 42,
        encrypted: false,
      });
    },
  },
  {
    name: "headerExtensions.uri modification",
    transform: (p) => { p.headerExtensions[0].uri = "uri"; },
    check: (p) => { return p.headerExtensions.length != 0; },
  },
  {
    name: "headerExtensions.id modification",
    transform: (p) => {
      p.headerExtensions[0].id = p.headerExtensions[0].id + 1;
    },
    check: (p) => {
      return p.headerExtensions.length != 0;
    },
  },
  {
    name: "headerExtensions.encrypted modification",
    transform: (p) => {
      p.headerExtensions[0].encrypted = !p.headerExtensions[0].encrypted;
    },
    check: (p) => {
      return p.headerExtensions.length != 0;
    },
  },
  {
    name: "headerExtensions.encrypted removed",
    transform: (p) => {
      p.headerExtensions[0].encrypted = !p.headerExtensions[0].encrypted;
    },
    check: (p) => {
      return p.headerExtensions.length != 0
        && p.headerExtensions[0].encrypted !== undefined;
    },
  },
  {
    name: "rtcp.cname modification",
    transform: (p) => { p.rtcp.cname = "cname"; },
  },
  {
    name: "rtcp.reducedSize modification",
    transform: (p) => { p.rtcp.reducedSize = !p.rtcp.reducedSize; },
  },
];

for (testCase of readOnlyParameters) {
  promise_test((async (name, transform, check, t) => {
    let pc1 = new RTCPeerConnection();
    let pc2 = new RTCPeerConnection();

    let stream = await navigator.mediaDevices.getUserMedia({ video: true }, 1);
    let videoSender = pc1.addTrack(stream.getVideoTracks()[0], stream);
    await exchangeOfferAnswer(pc1, pc2);

    let videoParameters = videoSender.getParameters();
    if (!check || check(videoParameters)) {
      transform(videoParameters);
      return promise_rejects_dom(t,
        "InvalidModificationError",
        videoSender.setParameters(videoParameters));
    }
  }).bind(undefined, testCase.name, testCase.transform, testCase.check),
  'video setParameters() check for ' + testCase.name);
}

for (testCase of readOnlyParameters) {
  promise_test((async (name, transform, check, t) => {
    let pc1 = new RTCPeerConnection();
    let pc2 = new RTCPeerConnection();

    let stream = await navigator.mediaDevices.getUserMedia({ audio: true }, 1);
    let audioSender = pc1.addTrack(stream.getAudioTracks()[0], stream);
    await exchangeOfferAnswer(pc1, pc2);

    let audioParameters = audioSender.getParameters();
    if (!check || check(audioParameters)) {
      transform(audioParameters);
      return promise_rejects_dom(t,
          "InvalidModificationError",
          audioSender.setParameters(audioParameters));
    }
  }).bind(undefined, testCase.name, testCase.transform, testCase.check),
  'audio setParameters() check for ' + testCase.name);
}

var requiredParameters = [
  {
    name: "transactionId is required",
    transform: (p) => { delete p.transactionId; },
  },
  {
    name: "codecs is required",
    transform: (p) => { delete p.codecs; },
  },
  {
    name: "codecs.payloadType is required",
    transform: (p) => { delete p.codecs[0].payloadType; },
  },
  {
    name: "codecs.mimeType is required",
    transform: (p) => { delete p.codecs[0].mimeType; },
  },
  {
    name: "codecs.clockRate is required",
    transform: (p) => { delete p.codecs[0].clockRate; },
  },
  {
    name: "encodings is required",
    transform: (p) => { delete p.encodings; },
  },
  {
    name: "headerExtensions is required",
    transform: (p) => { delete p.headerExtensions; },
  },
  {
    name: "headerExtensions.uri is required",
    transform: (p) => { delete p.headerExtensions[0].uri; },
  },
  {
    name: "headerExtensions.id is required",
    transform: (p) => { delete p.headerExtensions[0].id; },
  },
  {
    name: "rtcp is required",
    transform: (p) => { delete p.rtcp; },
  },
];

for (testCase of requiredParameters) {
  promise_test((async (name, transform, check, t) => {
    let pc1 = new RTCPeerConnection();
    let pc2 = new RTCPeerConnection();

    let stream = await navigator.mediaDevices.getUserMedia({ video: true }, 1);
    let videoSender = pc1.addTrack(stream.getVideoTracks()[0], stream);
    await exchangeOfferAnswer(pc1, pc2);

    let videoParameters = videoSender.getParameters();
    transform(videoParameters);
      return promise_rejects_js(t,
        TypeError,
        videoSender.setParameters(videoParameters));
  }).bind(undefined, testCase.name, testCase.transform, testCase.check),
  'video setParameters() check for ' + testCase.name);
}

promise_test(async t => {
  let pc1 = new RTCPeerConnection();
  t.add_cleanup(() => pc1.close());
  let pc2 = new RTCPeerConnection();

  t.add_cleanup(() => pc2.close());

  let stream = await navigator.mediaDevices.getUserMedia({video:true});
  let videoSender = pc1.addTrack(stream.getVideoTracks()[0], stream);

  await exchangeOfferAnswer(pc1, pc2);
  let videoParameters = videoSender.getParameters();
  videoParameters.encodings[0].maxBitrate = 50;
  await videoSender.setParameters(videoParameters);

  videoParameters = videoSender.getParameters();
  assert_equals(videoParameters.encodings[0].maxBitrate, 50);
}, 'setParameters() set low maxBitrate value');

/**
  * Helper functions to tests.
  */
async function exchangeOfferAnswer(localPc, remotePc) {
  let offer = await localPc.createOffer();
  await localPc.setLocalDescription(offer);
  await remotePc.setRemoteDescription(offer);

  let answer = await remotePc.createAnswer();
  await remotePc.setLocalDescription(answer);
  await localPc.setRemoteDescription(answer);
}

</script>
</body>
</html>
